package cn.doourbest.plugin.common;

import cn.doourbest.plugin.common.annotation.GenerateCustomer;
import com.google.auto.service.AutoService;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.TypeTag;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.*;
import com.sun.tools.javac.util.List;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import java.util.*;

/**
 * @Author: zhangsc
 * @Date: 2020/4/19 16:06
 * @Description: 生成常用的po
 */
@AutoService(Processor.class)
@SupportedAnnotationTypes("cn.doourbest.plugin.common.annotation.GenerateCustomer")
public class GenerateBOProcessor extends AbstractProcessor {

    private JavacTrees trees;
    private TreeMaker treeMaker;
    private Names names;



    @Override
    public void init(ProcessingEnvironment procEnv) {
        super.init(procEnv);
        this.trees = JavacTrees.instance(procEnv);
        Context context = ((JavacProcessingEnvironment) procEnv).getContext();
        this.treeMaker = TreeMaker.instance(context);
        this.names = Names.instance(context);
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return super.getSupportedAnnotationTypes();
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return super.getSupportedSourceVersion();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (roundEnvironment.processingOver() ) {
            return false;
        }
        //获取所有的带注解的类
        Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(GenerateCustomer.class);
        Iterator<? extends Element> iterator = elementsAnnotatedWith.iterator();
        while (iterator.hasNext()) {
            Element next = iterator.next();
            GenerateCustomer annotation = next.getAnnotation(GenerateCustomer.class);

            boolean bigDecimalOpen = annotation.bigDecimalOpen();
            String bigDecimalImport = annotation.bigDecimalImport();
            String bigDecimalMethod = annotation.bigDecimalMethod();
            String bigDecimalSuffix = annotation.bigDecimalSuffix();

            boolean dateOpen = annotation.dateOpen();
            String dateSuffix = annotation.dateSuffix();

            Map<String,java.util.List<JCTree.JCVariableDecl>> jcVarMap = new HashMap<>();
            Map<String,JCTree.JCMethodDecl> jcMethodMap = new HashMap<>();

            JCTree.JCClassDecl findCLass = returnMap(jcVarMap,jcMethodMap,next);
            if(findCLass == null){
                continue;
            }

            if(dateOpen){
                dealDateField(jcVarMap,jcMethodMap,findCLass,dateSuffix);
            }

            if(bigDecimalOpen){
                if(bigDecimalImport.trim().length() == 0){
                    continue;
                }
                if(bigDecimalMethod.trim().length() == 0){
                    continue;
                }

                dealBigDecimalField(jcVarMap,jcMethodMap,findCLass,next,bigDecimalImport,bigDecimalMethod,bigDecimalSuffix);
            }

        }

        return false;
    }

    private void dealBigDecimalField(Map<String,java.util.List<JCTree.JCVariableDecl>> jcVarMap,
                               Map<String,JCTree.JCMethodDecl> jcMethodMap,
                                     JCTree.JCClassDecl findCLass,Element next,String bigDecimalImport,String bigDecimalMethod,String bigDecimalSuffix
    ){

        java.util.List<JCTree.JCVariableDecl> bigDecimalType = jcVarMap.get("java.math.BigDecimal");

        JCTree.JCCompilationUnit jcCompilationUnit = toUnit(next);
        if(bigDecimalType != null && bigDecimalType.size() > 0) {
            for (JCTree.JCVariableDecl jcVariableDecl : bigDecimalType) {

                jcCompilationUnit.defs = jcCompilationUnit.defs.prepend(addImport(bigDecimalImport));

                JCTree.JCVariableDecl newVarDecl = addField(jcVariableDecl.name.toString() + bigDecimalSuffix, "java.lang.String");

                JCTree.JCMethodDecl setMethod = jcMethodMap.get(setMethodName(jcVariableDecl.name).toString());

                if(setMethod != null){

                    modifyMethodBigDecimalBody(setMethod, newVarDecl,bigDecimalImport,bigDecimalMethod);
                }else{
                    JCTree.JCMethodDecl methodDecl = makeSetterMethodDecl(jcVariableDecl);
                    modifyMethodDateBody(methodDecl, newVarDecl);

                    findCLass.defs = findCLass.defs.prepend(methodDecl);

                    JCTree.JCMethodDecl getMethod = jcMethodMap.get(getMethodName(jcVariableDecl.name).toString());
                    if(getMethod == null){
                        findCLass.defs = findCLass.defs.prepend(makeGetterMethodDecl(jcVariableDecl));
                    }
                }

                findCLass.defs = findCLass.defs.prepend(newVarDecl);
                findCLass.defs = findCLass.defs.prepend(makeGetterMethodDecl(newVarDecl));
                findCLass.defs = findCLass.defs.prepend(makeSetterMethodDecl(newVarDecl));
            }
        }
    }

    private void dealDateField(Map<String,java.util.List<JCTree.JCVariableDecl>> jcVarMap,
                               Map<String,JCTree.JCMethodDecl> jcMethodMap,
                               JCTree.JCClassDecl findCLass,String dateSuffix){
        java.util.List<JCTree.JCVariableDecl> dateType = jcVarMap.get("java.util.Date");
        if(dateType != null && dateType.size() > 0) {
            for (JCTree.JCVariableDecl jcVariableDecl : dateType) {
                JCTree.JCVariableDecl newVarDecl = addField(jcVariableDecl.name.toString() + dateSuffix, "java.lang.Long");

                JCTree.JCMethodDecl setMethod = jcMethodMap.get(setMethodName(jcVariableDecl.name).toString());
                if(setMethod != null){

                    modifyMethodDateBody(setMethod, newVarDecl);


                }else{
                    JCTree.JCMethodDecl methodDecl = makeSetterMethodDecl(jcVariableDecl);
                    modifyMethodDateBody(methodDecl, newVarDecl);

                    findCLass.defs = findCLass.defs.prepend(methodDecl);

                    JCTree.JCMethodDecl getMethod = jcMethodMap.get(getMethodName(jcVariableDecl.name).toString());
                    if(getMethod == null){
                        findCLass.defs = findCLass.defs.prepend(makeGetterMethodDecl(jcVariableDecl));
                    }

                }

                findCLass.defs = findCLass.defs.prepend(newVarDecl);
                findCLass.defs = findCLass.defs.prepend(makeGetterMethodDecl(newVarDecl));
                findCLass.defs = findCLass.defs.prepend(makeSetterMethodDecl(newVarDecl));
            }
        }
    }


    private JCTree.JCClassDecl returnMap(Map<String,java.util.List<JCTree.JCVariableDecl>> jcVarMap,
                                         Map<String,JCTree.JCMethodDecl> jcMethodMap,Element next){
        JCTree.JCClassDecl jcClassDecl = null;

        JCTree.JCCompilationUnit jcCompilationUnit = toUnit(next);
        for (JCTree def : jcCompilationUnit.defs) {
            if (def instanceof JCTree.JCClassDecl) {
                jcClassDecl =  (JCTree.JCClassDecl) def;
                for (JCTree jcTree : jcClassDecl.defs) {
                    if (jcTree instanceof JCTree.JCVariableDecl) {
                        JCTree.JCVariableDecl jcv = (JCTree.JCVariableDecl) jcTree;
                        if(jcv.vartype.type == null){
                            jcVarMap.computeIfAbsent(jcv.vartype.toString(),x -> new ArrayList<>());
                            jcVarMap.get(jcv.vartype.toString()).add(jcv);
                        }else{
                            jcVarMap.computeIfAbsent(jcv.vartype.type.toString(),x -> new ArrayList<>());
                            jcVarMap.get(jcv.vartype.type.toString()).add(jcv);
                        }
                    }else if (jcTree instanceof JCTree.JCMethodDecl) {
                        JCTree.JCMethodDecl methodDecl = (JCTree.JCMethodDecl) jcTree;
                        jcMethodMap.put( methodDecl.name.toString(),methodDecl);
                    }
                }
                break;
            }
        }
        return jcClassDecl;
    }


    //获取指定  类型的field
    private java.util.List<JCTree.JCVariableDecl> getMyTypeByStr(String type,com.sun.tools.javac.util.List<JCTree> jcTrees){
        java.util.List<JCTree.JCVariableDecl> list = new ArrayList<>();
        for (JCTree def : jcTrees) {
            if (def instanceof JCTree.JCClassDecl) {
                JCTree.JCClassDecl classDecl = (JCTree.JCClassDecl) def;
                for (JCTree jcTree : classDecl.defs) {
                    if (jcTree instanceof JCTree.JCVariableDecl) {
                        JCTree.JCVariableDecl jcv = (JCTree.JCVariableDecl) jcTree;

                        if(jcv.vartype.type == null){
                            if (jcv.vartype.toString().equals(type)) {
                                list.add(jcv);
                            }
                        }else{
                            if (jcv.vartype.type.toString().equals(type)) {
                                list.add(jcv);
                            }
                        }
                    }
                }
            }
        }
        return list;
    }

    //获取当前对象的 class的结构
    private JCTree.JCClassDecl getClassDeclByStr(com.sun.tools.javac.util.List<JCTree> jcTrees){
        for (JCTree def : jcTrees) {
            if (def instanceof JCTree.JCClassDecl) {
                return (JCTree.JCClassDecl) def;
            }
        }
        return null;
    }

    //查询 当前指定field 的set方法
    private JCTree.JCMethodDecl getSetMethod(JCTree.JCVariableDecl variableDecl,com.sun.tools.javac.util.List<JCTree> jcTrees){
        for (JCTree def : jcTrees) {
            if (def instanceof JCTree.JCClassDecl) {
                JCTree.JCClassDecl classDecl = (JCTree.JCClassDecl) def;
                for (JCTree de : classDecl.defs) {
                    if (de instanceof JCTree.JCMethodDecl
                            && setMethodName(variableDecl.name).toString().equals(((JCTree.JCMethodDecl) de).name.toString())) {
                        return (JCTree.JCMethodDecl) de;
                    }
                }
            }
        }
        return null;
    }

    /**
     * @Description: 新增 field
     * @Author:  zhangsc
     * @param fieldName: field名称
     * @param type: field 类型
    */
    private JCTree.JCVariableDecl addField(String fieldName,String type) {
        //方法的访问级别
        JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PRIVATE);
        Name name = getNameFromString(fieldName);
        return treeMaker.VarDef(modifiers, name, memberAccess(type), null);
    }

    //新增get方法
    private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
        //方法的访问级别
        JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
        //方法名称
        Name methodName = getMethodName(jcVariableDecl.getName());
        //设置返回值类型
        JCTree.JCExpression returnMethodType = jcVariableDecl.vartype;
        ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
        statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(getNameFromString("this")), jcVariableDecl.getName())));
        //设置方法体
        JCTree.JCBlock methodBody = treeMaker.Block(0, statements.toList());
        //构建方法
        return treeMaker.MethodDef(modifiers, methodName, returnMethodType, List.nil(), List.nil(), List.nil(), methodBody, null);
    }

    /**
     * 创建set方法
     */
    private JCTree.JCMethodDecl makeSetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
        try {
            //方法的访问级别
            JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
            //定义方法名
            Name methodName = setMethodName(jcVariableDecl.getName());
            //定义返回值类型
            JCTree.JCExpression returnMethodType = treeMaker.Type((Type) (Class.forName("com.sun.tools.javac.code.Type$JCVoidType").newInstance()));
            ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
            statements.append(treeMaker.Exec(treeMaker.Assign(treeMaker.Select(treeMaker.Ident(getNameFromString("this")), jcVariableDecl.getName()), treeMaker.Ident(jcVariableDecl.getName()))));
            //定义方法体
            JCTree.JCBlock methodBody = treeMaker.Block(0, statements.toList());
            List<JCTree.JCTypeParameter> methodGenericParams = List.nil();
            //定义入参
            JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER, List.nil()), jcVariableDecl.name, jcVariableDecl.vartype, null);
            //设置入参
            List<JCTree.JCVariableDecl> parameters = List.of(param);
            //构建新方法
            return treeMaker.MethodDef(modifiers, methodName, returnMethodType, methodGenericParams, parameters, List.nil(), methodBody, null);
        } catch (Exception e) {
            System.err.println(e);
        }
        return null;

    }

    /**
     * 修改方法的body 给date方法 增加 if判断 和 复制
     *    public void setDate(Date date) {
     *         if (date != null) {
     *             this.dateLong = date.getTime();
     *         }
     *
     *         this.date = date;
     *     }
     */
    private void modifyMethodDateBody(JCTree.JCMethodDecl methodDecl, JCTree.JCVariableDecl newJCV) {
        try {
            JCTree.JCBlock body = methodDecl.body;
            JCTree.JCVariableDecl jcVariableDecl = methodDecl.getParameters().get(0);


            JCTree.JCFieldAccess getTime = treeMaker.Select(treeMaker.Ident(jcVariableDecl.getName()), getNameFromString("getTime"));


            JCTree.JCAssign aThis = treeMaker.Assign(
                    treeMaker.Select(treeMaker.Ident(getNameFromString("this")), newJCV.getName()),
                    treeMaker.Apply(null,getTime,List.nil()));


            JCTree.JCParens parens = treeMaker.Parens(
                    treeMaker.Binary(
                            JCTree.Tag.NE,
                            treeMaker.Ident(jcVariableDecl.getName()),
                            treeMaker.Literal(TypeTag.BOT, null)
                    )
            );

            JCTree.JCExpressionStatement exec = this.treeMaker.Exec(aThis);

            JCTree.JCIf anIf = this.treeMaker.If(
                        parens
                    , exec,
                    null);
            body.stats = body.stats.prepend(anIf);
        } catch (Exception e) {
            System.err.println(e);
        }

    }

    //新增 import
    private JCTree.JCImport addImport(String path){
        return treeMaker.Import(memberAccess(path), false);
    }
    /**
     * 修改方法的body
     *   public void setBigDecimal(BigDecimal bigDecimal) {
     *         if (bigDecimal != null) {
     *             this.bigDecimalStr = MoneyUtil.bigDecimalTranfer(bigDecimal.toString());
     *         }
     *
     *         this.bigDecimal = bigDecimal;
     *     }
     */
    private void modifyMethodBigDecimalBody(JCTree.JCMethodDecl methodDecl, JCTree.JCVariableDecl newJCV,String bigDecimalImport,String bigDecimalMethod) {
        try {
            JCTree.JCBlock body = methodDecl.body;
            JCTree.JCVariableDecl jcVariableDecl = methodDecl.getParameters().get(0);


            String[] split = bigDecimalImport.split("\\.");
            int size = split.length;


            JCTree.JCFieldAccess bigDecimalTranfer = treeMaker.Select(treeMaker.Ident(getNameFromString(split[size - 1])), getNameFromString(bigDecimalMethod));
            JCTree.JCFieldAccess bigDecimalToString = treeMaker.Select(treeMaker.Ident(getNameFromString(jcVariableDecl.name.toString())), getNameFromString("toString"));

            JCTree.JCMethodInvocation bigDecimalToStringMethod = treeMaker.Apply(null, bigDecimalToString, List.nil());


            JCTree.JCMethodInvocation apply = treeMaker.Apply(null, bigDecimalTranfer, List.of(bigDecimalToStringMethod));


            JCTree.JCAssign aThis = treeMaker.Assign(
                    treeMaker.Select(treeMaker.Ident(getNameFromString("this")), newJCV.getName()),
                    apply);


            JCTree.JCParens parens = treeMaker.Parens(
                    treeMaker.Binary(
                            JCTree.Tag.NE,
                            treeMaker.Ident(jcVariableDecl.getName()),
                            treeMaker.Literal(TypeTag.BOT, null)
                    )
            );

            JCTree.JCExpressionStatement exec = this.treeMaker.Exec(aThis);

            JCTree.JCIf anIf = this.treeMaker.If(
                    parens
                    , exec,
                    null);
            body.stats = body.stats.prepend(anIf);
        } catch (Exception e) {
            System.err.println(e);
        }

    }

    // 字符串 转换为 多层 的对象
    private JCTree.JCExpression memberAccess(String components) {
        String[] componentArray = components.split("\\.");
        JCTree.JCExpression expr = treeMaker.Ident(getNameFromString(componentArray[0]));
        for (int i = 1; i < componentArray.length; i++) {
            expr = treeMaker.Select(expr, getNameFromString(componentArray[i]));
        }
        return expr;
    }

    private Name getNameFromString(String s) {
        return names.fromString(s);
    }

    private Name getMethodName(Name name) {
        String s = name.toString();
        return getNameFromString("get" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
    }


    private Name setMethodName(Name name) {
        String s = name.toString();
        return getNameFromString("set" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
    }


    private JCTree.JCCompilationUnit toUnit(Element element) {
        return (JCTree.JCCompilationUnit) trees.getPath(element).getCompilationUnit();
    }

//
//    private void ge(RoundEnvironment roundEnvironment) {
//        Iterator<? extends Element> iterator = roundEnvironment.getRootElements().iterator();
//        while (iterator.hasNext()) {
//            Element next = iterator.next();
//            GenerateDateLong annotation = next.getAnnotation(GenerateDateLong.class);
//            if (annotation == null) {
//                continue;
//            }
//
//
//            List<? extends Element> enclosedElements = next.getEnclosedElements();
//
//            List<String> listDateName = new ArrayList<>();
//
//            for (Element enclosedElement : enclosedElements) {
//                if (enclosedElement instanceof VariableElement) {
//                    if ("java.util.Date".equals(enclosedElement.asType().toString())) {
//                        listDateName.add(enclosedElement.getSimpleName().toString());
//                    }
//                }
//            }
//
//            if (listDateName.size() == 0) break;
//
//            String allName = ((TypeElement) next).getQualifiedName().toString();
//            int i = allName.lastIndexOf(".");
//            String pre = allName.substring(0, i);
//            String suff = allName.substring(i + 1) + ".java";
//
//            StringBuilder stringBuilder = new StringBuilder();
//            InputStream inputStream = null;
//            BufferedReader reader = null;
//            try {
//                byte[] ss = new byte[0];
//                FileObject resource = this.processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, pre, suff);
//
//                inputStream = resource.openInputStream();
//                reader = new BufferedReader(new InputStreamReader(inputStream));
//
//                boolean isAdd = false;
//                String nowName = null;
//                while (true) {
//                    String s = reader.readLine();
//                    if (s == null) {
//                        break;
//                    }
//                    if (isAdd) {
//                        stringBuilder.append("        if( ").append(nowName).append(" != null){").append("\n");
//                        stringBuilder.append("            this.").append(nowName).append("Str = ").append(nowName).append(".getTime();").append("\n");
//                        stringBuilder.append("        }").append("\n");
//                    }
//
//                    boolean isCheck = false;
//                    for (String nn : listDateName) {
//                        if (s.contains("set" + nn.substring(0, 1).toUpperCase() + nn.substring(1))) {
//                            isCheck = true;
//                            isAdd = true;
//                            nowName = nn;
//                            break;
//                        }
//                    }
//                    if (!isCheck) {
//                        isAdd = false;
//                    }
//                    if (isAdd) {
//                        String newName = nowName + LONGSUFF;
//                        String getNewName = "get" + nowName.substring(0, 1).toUpperCase() + nowName.substring(1) + LONGSUFF;
//                        String setNewName = "set" + nowName.substring(0, 1).toUpperCase() + nowName.substring(1) + LONGSUFF;
//                        stringBuilder.append("    private Long ").append(newName).append(";").append("\n");
//
//                        stringBuilder.append("    private Long ").append(getNewName).append("() {").append("\n");
//                        stringBuilder.append("        return ").append(newName).append(";").append("\n");
//                        stringBuilder.append("    }").append("\n");
//                        stringBuilder.append("    private void ").append(setNewName).append("( Long ").append(newName).append(") {").append("\n");
//                        stringBuilder.append("        this.").append(newName).append(" = ").append(newName).append(";").append("\n");
//                        stringBuilder.append("    }").append("\n");
//                    }
//
//                    stringBuilder.append(s).append("\n");
//
//                }
//
//                FileObject resource1 = processingEnv.getFiler().createSourceFile(allName);
//                Writer writer = resource1.openWriter();
//                writer.write(stringBuilder.toString());
//                writer.flush();
//                writer.close();
//
//
//            } catch (IOException e) {
//                try {
//                    inputStream.close();
//                } catch (IOException ex) {
//                    ex.printStackTrace();
//                }
//                try {
//                    reader.close();
//                } catch (IOException ex) {
//                    ex.printStackTrace();
//                }
//            }
//
//
//        }
//    }
}
